//xulunit.js
var _testRunner;

//Global Variables
var suiteOnExecution = null;

//Constant values
var index = 1;
const XULUNIT_PASS = index++;
const XULUNIT_FAIL = index++;
const XULUNIT_BREAK = index++;
const XULUNIT_UNKNOWN = index++;
const XULUNIT_ASSERT = index++;
const XULUNIT_ASSERTTRUE = index++;
const XULUNIT_ASSERTFALSE = index++;
const XULUNIT_ASSERTEQUALS = index++;
const XULUNIT_ASSERTNOTEQUALS = index++;
const XULUNIT_ASSERTNULL = index++;
const XULUNIT_ASSERTNOTNULL = index++;
const XULUNIT_ASSERTUNDEFINED = index++;
const XULUNIT_ASSERTNOTUNDEFINED = index++;
const XULUNIT_ASSERTNAN = index++;
const XULUNIT_ASSERTNOTNAN = index++;
const XULUNIT_ASSERTREGEXP = index++;
const XULUNIT_ASSERTNOTREGEXP = index++;
const XULUNIT_ASSERTTYPEOF = index++;
const XULUNIT_ASSERTNOTTYPEOF = index++;
const XULUNIT_TESTSUITE = index++;
const XULUNIT_TESTCASE = index++;


function TestSuite(name){
  this.type = XULUNIT_TESTSUITE;
  this.name = name;
  this.result = XULUNIT_UNKNOWN;
  this.testCase = new Array();
  this.testCaseOnExecution = "";
  this.testChild = this.testCase;

  this.add = addTestSuite;
  this.run = runTestSuite;
  this.prepare = prepareTestSuite;
  this.execute = executeTestSuite;
  this.close = closeTestSuite;
  this.getResult = getResultTestSuite;

  //Adds a TestCase to the TestSuite
  function addTestSuite(testCase){
    this.testCase[this.testCase.length] = testCase;
  }

  //Executes TestCases in the TestSuite and records results
  //by calling prepare, execute and close
  function runTestSuite(){
    suiteOnExecution = this;
    this.prepare();
    this.execute();
    this.close();
  }

  //Does any action needed to prepare execution of TestSuite
  function prepareTestSuite(){

    //To be overriden
  
  }

  //Actually executes TestCases in TestSuite
  function executeTestSuite(){
    for (var i=0; i<this.testCase.length; i++){
      this.testCaseOnExecution = this.testCase[i];
      //try{
      	this.testCaseOnExecution.run();
      //}
      /*catch(ex){
		this.testCaseOnExecution.result = XULUNIT_BREAK;
		this.testCaseOnExecution.breakMessage = ex;
      }*/
    }
  }

  //Does any action needed to close the execution of TestSuite
  function closeTestSuite(){
    //To be overriden
  
  }

  //Returns a boolean with the result of the overall TestCases in TestSuite
   function getResultTestSuite(){
    if (this.testCase.length > 0) {
    	this.result = this.testCase[0].getResult();
    }
    for (var i=1; i<this.testCase.length; i++) {
      if (this.testCase[i].getResult() != XULUNIT_PASS) {
      	this.result = XULUNIT_FAIL;
      }
     }

    return this.result;
  }

}

function TestCase(name, testFunction){
  this.type = XULUNIT_TESTCASE;
  this.name = name;
  this.testFunction = testFunction;
  this.result = XULUNIT_UNKNOWN;
  this.test = new Array();
  this.testOnExecution = null;
  this.testChild = this.test;

  this.run = runTestCase;
  this.getResult = getResultTestCase;
  this.launchTest = launchTestTestCase;

  //Runs the TestCase and records the result
  function runTestCase(){
  	this.test = new Array();
    this.testFunction();
  }
   
  //Sets and returns the result of the TestCase
  function getResultTestCase(){
    if (this.result == XULUNIT_UNKNOWN) {	//If a test breaks, the result will be XULUNIT_BREAK
      if (this.test.length > 0) {
      	this.result = this.test[0].getResult();
      }
    }
	for (var i=1; i<this.test.length; i++) {
	  if (this.test[i].result != XULUNIT_PASS) this.result = XULUNIT_FAIL;
	 }

    return this.result;
  }
  
  //Called when a test is launched to record its information
  //Returns a pointer to the new test
  function launchTestTestCase(testType, name){
    currentTest = new Test(testType, name);
    this.test[this.test.length] = currentTest;
    testOnExecution = currentTest;

    return currentTest;
  }

}

function Test(testType, name){
  this.type = testType;
  this.name = name;
  this.result = XULUNIT_UNKNOWN;
  this.breakMessage = "";
  this.testChild = new Array();
  
  this.passes = passesTest;
  this.fails = failsTest;
  this.breaks = breaksTest;
  this.getResult = getResultTest;
  
  //Called when a test passes to record its results
  function passesTest(){
    this.result = XULUNIT_PASS;
  }

  //Called when a test fails to record its results
  function failsTest(){
    this.result = XULUNIT_FAIL;
  }

  //Called when a test breaks to record its results
  function breaksTest(message){
    this.result = XULUNIT_BREAK;
    this.breakMessage = message;
  }
  
  //Returns the result of a test
  function getResultTest(){
    return this.result;
  }
}

//Test Functions

//Asserts toEval expression equals true
function assert(name, toEval){
  _assert(XULUNIT_ASSERT, false, name, true, toEval);
}

//Asserts toEval expression equals true
function assertTrue(name, toEval){
  _assert(XULUNIT_ASSERTTRUE, false, name, true, toEval);
}

//Asserts toEval expression equals false
function assertFalse(name, toEval){
  _assert(XULUNIT_ASSERTFALSE, false, name, false, toEval);
}

//Asserts toEval expression equals value
function assertEquals(name, value, toEval){
  _assert(XULUNIT_ASSERTEQUALS, false, name, value, toEval);
}

//Asserts toEval expression does not equals value
function assertNotEquals(name, value, toEval){
  _assert(XULUNIT_ASSERTNOTEQUALS, true, name, value, toEval);
}

//Asserts toEval expression is null
function assertNull(name, toEval){
  _assert(XULUNIT_ASSERTNULL, false, name, null, toEval);
}

//Asserts toEval expression is not null
function assertNotNull(name, toEval){
  _assert(XULUNIT_ASSERTNOTNULL, true, name, null, toEval);
}

//Asserts toEval expression is undefined
function assertUndefined(name, toEval){
  _assert(XULUNIT_ASSERTUNDEFINED, false, name, undefined, toEval);
}

//Asserts toEval expression is not undefined
function assertNotUndefined(name, toEval){
  _assert(XULUNIT_ASSERTNOTUNDEFINED, true, name, undefined, toEval);
}

//Asserts toEval expression is Nan (Not a Number)
function assertNaN(name, toEval){
  _assert(XULUNIT_ASSERTNAN, false, name, true, isNaN(toEval));
}

//Asserts toEval expression is not Nan (Not a Number). I.e. it is a number
function assertNotNaN(name, toEval){
  _assert(XULUNIT_ASSERTNOTNAN, true, name, true, isNaN(toEval));
}

//Asserts the expression to eval follows the regExp indicated.
function assertRegExp(name, regExp, toEval){
  _assert(XULUNIT_ASSERTREGEXP, false, name, regExp, toEval.match(regExp));
}

//Asserts the expression to eval does not follow the regExp indicated.
function assertNotRegExp(name, regExp, toEval){
  _assert(XULUNIT_ASSERTNOTREGEXP, true, name, regExp, toEval.match(regExp));
}

//Asserts the expression to eval is of the indicated type.
function assertTypeOf(name, type, toEval){
  _assert(XULUNIT_ASSERTTYPEOF, false, name, type, typeof(toEval));
}

//Asserts the expression to eval is not of the indicated type.
function assertNotTypeOf(name, type, toEval){
  _assert(XULUNIT_ASSERTNOTTYPEOF, true, name, type, typeof(toEval));
}

//Real assertion function for any case
function _assert(type, negate, name, value, toEval){
  currentTest = suiteOnExecution.testCaseOnExecution.launchTest(type, name);
  try{
    if (!negate){
      if (toEval == value) {
      	currentTest.passes();
      } else {
      	currentTest.fails();
      }
    }
    else{
      if (toEval != value) { 
      	currentTest.passes();
      } else {
      	currentTest.fails();
      }
    }
  }
  catch(ex){
    currentTest.breaks(ex);
  }
}


//Class TestRunner (XulUnit User Interface)
function TestRunner(){
  //Creates a XulUnit User Interface object (called TestRunner) for the testSuite
  //TestRunner is used to present results on a graphical predefined (testrunner.xul) interface

  this.testSuite = new Array();	  //All suites to be run
  this._testObject = new Array(); //All testObjects (TestSuites, TestCases, Tests) to be run
  this._window = null;

  this.add = addTestRunner;
  this.launch = launchTestRunner;
  this.run = runTestRunner;
  this.showResults = showResultsTestRunner;
  this._getTreeitem = _getTreeitemTestRunner;
  this.showDetails = showDetailsTestRunner;
  //this.showTestCase = showTestCaseTestRunner;
  //this.showTest = showTestTestRunner;

  //Adds a testSuite to be run
  function addTestRunner(testSuite){
    this.testSuite[this.testSuite.length] = testSuite;
  }

  //Creates a new window ready to launch tests
  function launchTestRunner(){
    _testRunner = this; //Set the global variable to remember which TR to execute at the new window
    this._window = window.open("testrunner.xul", "testRunnerWindow", "chrome, centerscreen, resizable, width=600, height=400");
  }

  //Runs all TestSuites and show results
  function runTestRunner(){
    for (var i=0; i<this.testSuite.length; i++) {
      this.testSuite[i].run();
     }
    this.showResults();
  }
  
  //Shows data for the TestSuite
  function showResultsTestRunner(){
    //Total Data
    var totalTestSuites;
    var totalTestCases = 0;
    var totalTests = 0;
    
    totalTestSuites = this.testSuite.length;
    for (var i=0; i<this.testSuite.length; i++){
      totalTestCases += this.testSuite[i].testChild.length;
      for (var j=0; j<this.testSuite[i].testChild.length; j++) {
		totalTests += this.testSuite[i].testChild[j].testChild.length;
		}
    }
    
    this._window.document.getElementById("labelTotalTestSuites").value = totalTestSuites;
    this._window.document.getElementById("labelTotalTestCases").value = totalTestCases;
    this._window.document.getElementById("labelTotalTests").value = totalTests;

    //Deletion of Results
    var treechildrenResults = this._window.document.getElementById("treechildrenResults");
    while(treechildrenResults.hasChildNodes()) {
      treechildrenResults.removeChild(treechildrenResults.firstChild)
     }
    
    //TreeResult Construction
    for (var i=0; i<this.testSuite.length; i++)
      treechildrenResults.appendChild(this._getTreeitem(this.testSuite[i]));
  }

  //Returns a treeitem for the testObject
  function _getTreeitemTestRunner(testObject){
    var resultTreeitem = document.createElement("treeitem");
    resultTreeitem.setAttribute("testObjectId", this._testObject.length);
    this._testObject[this._testObject.length] = testObject;

    var resultTreerow = document.createElement("treerow");
    resultTreeitem.appendChild(resultTreerow);
    
    var resultNameTreecell = document.createElement("treecell");
    resultNameTreecell.setAttribute("label", testObject.name);
    resultTreerow.appendChild(resultNameTreecell);
    
    var resultResultTreecell = document.createElement("treecell");
    resultResultTreecell.setAttribute("label", getResultText(testObject.getResult()));
    
    //Set the style for the Result
    switch (testObject.getResult()){
      case XULUNIT_PASS:
	resultResultTreecell.setAttribute("properties", "testPasses");
	break;
      case XULUNIT_FAIL:
	resultResultTreecell.setAttribute("properties", "testFails");
	break;
      case XULUNIT_BREAK:
	resultResultTreecell.setAttribute("properties", "testBreaks");
	break;
      default:
	resultResultTreecell.setAttribute("properties", "testUnknown");
    }
    resultTreerow.appendChild(resultResultTreecell);
    
    //Create a treechildren if testObject has Childs
    if (testObject.testChild.length > 0){
      resultTreeitem.setAttribute("container","true");
      resultTreeitem.setAttribute("open", false);
      
      var newTreechildren = document.createElement("treechildren");
      resultTreeitem.appendChild(newTreechildren);
      for(var i=0; i<testObject.testChild.length; i++) {
		newTreechildren.appendChild(this._getTreeitem(testObject.testChild[i]));
		}
    }
    
    return resultTreeitem;
  }
  
  //Shows details for a testObject (this means testSuite, testCase or test)
  function showDetailsTestRunner(){
    treeResults = this._window.document.getElementById("treeResults");
    testObject = this._testObject[treeResults.view.getItemAtIndex(treeResults.currentIndex).getAttribute("testObjectId")];
    
    this._window.document.getElementById("labelDetailType").value = getTestTypeText(testObject.type);
    this._window.document.getElementById("labelDetailNameComment").value = testObject.name;
    this._window.document.getElementById("labelDetailResult").value = getResultText(testObject.getResult());
    if (testObject.getResult() == XULUNIT_BREAK) {
      this._window.document.getElementById("labelDetailBreakMessage").value = testObject.breakMessage;
     }
  }
  
}//TestRunner Class

//Functions for constant translation 

//Returns test result on a human readble string
function getResultText(result){
  switch (result){
    case XULUNIT_PASS:
      return("PASS");
    case XULUNIT_FAIL:
      return("FAIL");
    case XULUNIT_BREAK:
      return("BREAK");
    case XULUNIT_UNKNOWN:
      return("UNKNOWN");
  }
}

//Returns test type on a human readble string
function getTestTypeText(type){
  switch (type){
    case XULUNIT_ASSERT:
      return("Assert");
    case XULUNIT_ASSERTTRUE:
      return("Assert True");
    case XULUNIT_ASSERTFALSE:
      return("Assert False");
    case XULUNIT_ASSERTEQUALS:
      return("Assert Equals");
    case XULUNIT_ASSERTNOTEQUALS:
      return("Assert Not Equals");
    case XULUNIT_ASSERTNULL:
      return("Assert Null");
    case XULUNIT_ASSERTNOTNULL:
      return("Assert Not Null");
    case XULUNIT_ASSERTUNDEFINED:
      return("Assert Undefined");
    case XULUNIT_ASSERTNOTUNDEFINED:
      return("Assert Not Undefined");
    case XULUNIT_ASSERTNAN:
      return("Assert NaN");
    case XULUNIT_ASSERTNOTNAN:
      return("Assert Not NaN");
    case XULUNIT_TESTSUITE:
      return("Test Suite");
    case XULUNIT_TESTCASE:
      return("Test Case");
  }
}
